/**
 * 
 */
package enron;

import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Date;
import jade.core.AID;
import jade.core.Agent;
import jade.core.behaviours.TickerBehaviour;
import jade.domain.DFService;
import jade.domain.FIPAException;
import jade.domain.FIPANames;
import jade.domain.FIPAAgentManagement.DFAgentDescription;
import jade.domain.FIPAAgentManagement.ServiceDescription;
import jade.lang.acl.ACLMessage;

public class BuyerAgent extends Agent {
	private static final long serialVersionUID = -6517788266935627543L;
	public static final NumberFormat energyFormat = new DecimalFormat("0.000");

	static class LocationType { 
		public double longitude;
		public double latitude;
	}

	private LocationType location = new LocationType();
	
	private Long wattage = 0L;
	private Double energyUsed = 0.0;
	private Long lastUpdate;
	
	private ArrayList<Appliance> appliances;
	private BuyerAgentUI window;
	private EnergyGrid myGrid;
	
	public boolean isPurchasing = false;

	@Override
	protected void setup() {
		Object[] args = getArguments();
		if(args != null && args.length > 0) {
			location.longitude = (Double)args[0];
			location.latitude = (Double)args[1];
			setPower(0L);
			myGrid = (EnergyGrid) args[2];
			setAppliances(args[3]);
		} else {
			setPower(0L);
			setAppliances(new ArrayList<Appliance>());
		}
		registerWithDirectoryService();
		
		lastUpdate = System.currentTimeMillis();
		window = new BuyerAgentUI(this);

		addBehaviour(new TickerBehaviour(this,250) {
			private static final long serialVersionUID = -6972736261803317042L;
			@Override
			protected void onTick() {
				((BuyerAgent) myAgent).updateEnergy();
			}
		});
		
		addBehaviour(new TickerBehaviour(this,30000) {
			private static final long serialVersionUID = 7793605825949286951L;
			@Override
			protected void onTick() {
				Long currentTime = System.currentTimeMillis();
				if(!((BuyerAgent)myAgent).isPurchasing) {
					((BuyerAgent)myAgent).setEnergyUsed(((BuyerAgent)myAgent).energyUsed + ((double)(currentTime-((BuyerAgent)myAgent).lastUpdate)/3600000.0) * ((double)((BuyerAgent)myAgent).wattage/1000.0));
					((BuyerAgent)myAgent).lastUpdate = currentTime;
					((BuyerAgent)myAgent).purchaseEnergy();
				}
			}
		});
		
		super.setup();
	}

	protected void registerWithDirectoryService() {
		DFAgentDescription dfd = new DFAgentDescription();
		dfd.setName(getAID()); 
	    ServiceDescription sd = new ServiceDescription();
	    sd.setType("energy-buyer");
	    sd.setName(getAID().getName());
	    dfd.addServices(sd);
	    try { 
	      DFService.register(this, dfd); 
	    } 
	    catch (FIPAException fe) { 
	      fe.printStackTrace(); 
	    }
	}
	
	private void purchaseEnergy() {
		isPurchasing = true;
		ACLMessage msg = new ACLMessage(ACLMessage.CFP);
		getVendorList();
		ArrayList<AID> vendorList = getVendorList();
  		for (AID vendor : vendorList) {
  			msg.addReceiver(vendor);
  			//System.out.println("Auction: "+ this.getLocalName() +" sending to " + vendor.getLocalName());
  		}
		msg.setProtocol(FIPANames.InteractionProtocol.FIPA_ITERATED_CONTRACT_NET);
		// We want to receive a reply in 10 secs
		msg.setReplyByDate(new Date(System.currentTimeMillis() + 10000));
		msg.setContent(energyUsed.toString());
		msg.addUserDefinedParameter("negotiationIteration", "0");
		addBehaviour(new PurchaseEnergyBehaviour(this,msg,vendorList.size()));
	}
	
	private ArrayList<AID> getVendorList() {
		// Update the list of seller agents
	    DFAgentDescription template = new DFAgentDescription(); 
	    ServiceDescription templateSd = new ServiceDescription(); 
	    ArrayList<AID> newVendorList = new ArrayList<AID>();
	    
	    templateSd.setType("energy-vendor"); 
	    template.addServices(templateSd); 
	    try { 
	      DFAgentDescription[] result = DFService.search(this, template);
	      //System.out.println("Received " + result.length +" agents.");
	      /*if(result.length == 0) {
	      	return newVendorList;
	      }*/
	      for(DFAgentDescription buyer : result) { 
	    	  //System.out.println("Received " + buyer.getName());
		      newVendorList.add(buyer.getName()); 
	      } 
	    } 
	    catch (FIPAException fe) { 
	      fe.printStackTrace(); 
	    }
	    return newVendorList;
	}

	/* (non-Javadoc)
	 * @see jade.core.Agent#takeDown()
	 */
	@Override
	protected void takeDown() {
		try {
			DFService.deregister(this);
		} catch (FIPAException e) {
			e.printStackTrace();
		}
		super.takeDown();
	}

	public Long getPower() {
		return wattage;
	}
	public void setPower(Long watts) {
		wattage = watts;
	}
	
	public void changePower(long watts) {
		updateEnergy();
		wattage += watts;
		myGrid.adjustLoad(watts);
		window.getLblWattageValue().setText(wattage.toString() + "W");
	}
	
	public void addPower(Long watts) {
		changePower(watts);
	}
	
	public void removePower(Long watts) {
		changePower(-watts);
	}
	
	private void updateEnergy() {
		Long currentTime = System.currentTimeMillis();
		setEnergyUsed(energyUsed + ((double)(currentTime-lastUpdate)/3600000.0) * ((double)wattage/1000.0));
		lastUpdate = currentTime;
		window.getLblEnergyValue().setText(energyFormat.format(energyUsed*1000.0) + "Wh");
		
		if(energyUsed > 2.0 && !isPurchasing) {
			purchaseEnergy();
		}
	}
	
	public void receiveEnergy(Double energyReceived) {
		updateEnergy();
//		System.out.print(energyFormat.format(energyUsed) + " - " + energyFormat.format(energyReceived) + " = ");
		energyUsed -= energyReceived;
//		System.out.println(energyFormat.format(energyUsed));
		window.getLblEnergyValue().setText(energyFormat.format(energyUsed*1000.0) + "Wh");
		//System.out.println(getAID().getLocalName()+": Received " + energyReceived + "Wh, now total energy is " + energyUsed + "kWh.");
	}

	public ArrayList<Appliance> getAppliances() {
		return appliances;
	}

	public void setAppliances(ArrayList<Appliance> appliances) {
		this.appliances = appliances;
		for(Appliance currentAppliance : this.appliances) {
			currentAppliance.setOwner(this);
			if(currentAppliance.isTurnedOn()) {
				wattage += currentAppliance.getWattage();
				myGrid.adjustLoad(currentAppliance.getWattage());
			}
		}
	}
	
	@SuppressWarnings("unchecked")
	private void setAppliances(Object applianceList) {
		setAppliances((ArrayList<Appliance>)applianceList);
	}
	
	public Double getEnergyUsed() {
		return energyUsed;
	}

	public void setEnergyUsed(Double energyUsed) {
		this.energyUsed = energyUsed;
	}

	public static NumberFormat getEnergyformat() {
		return energyFormat;
	}

	public Appliance[][] getApplianceArray() {
		Appliance[][] returnVal = new Appliance[appliances.size()][1];
		for(int i = 0; i < appliances.size(); i++) {
			returnVal[i][0] = appliances.get(i);
		}
		return returnVal;
	}
}
